package ms.unicorn.reasoning.subcribers;

import java.util.ArrayList;
import java.util.Collection;

import ms.unicorn.Element;
import ms.unicorn.NotificationService;
import ms.unicorn.NotificationService.Subscription;
import ms.unicorn.Relation;
import ms.unicorn.dsl.ILanguageElement;
import ms.unicorn.dsl.LanguageRegistry;
import ms.unicorn.dsl.unicornDSL.LanguageElement;
import ms.unicorn.reasoning.UnicornDSL;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EReference;

public class OriginalModelElementSubcriber implements NotificationService.Subscriber<Notification, Object>, UnicornDSL {
	
	public static Collection<Integer> PREFER_CHANNELS = new ArrayList<Integer>(2);
	
	static {
		PREFER_CHANNELS.add(NotificationService.CHANNEL_ELEMENT);
	}
	
	public OriginalModelElementSubcriber() {
		
	}
	
	@Override
	public Collection<Integer> getPreferChannels() {
		return PREFER_CHANNELS;
	}

	@Override
	public int hanldeSubscription(Object sender, int channel, Subscription<Notification, Object> subscription) {
		switch (channel) {
		case NotificationService.CHANNEL_ELEMENT:
			if (subscription.getEvent() == Notification.ADD || subscription.getEvent() == Notification.REMOVE && subscription.getData() instanceof Notification) {
				Object feature = subscription.getData().getFeature();
				if (feature instanceof EReference) {
					String name = ((EReference) feature).getName();
					if (name.equals("incoming") || name.equals("outgoing")) {
						Element notifier = (Element) subscription.getData().getNotifier();
						//System.out.println(subscription.getData().getFeature());
						ILanguageElement le = LanguageRegistry.INSTANCE.getLanguageElement(notifier);
						if (le != null && le.getObjectType() == ILanguageElement.LANGUAGE_ENTITY)
							updateOriginality(notifier);
					}
				}
				
			}
			break;
		}
		return NotificationService.SUBSCRIPTION_NORMAL;
	}
	
	public void updateOriginality(Element e) {
		boolean connectedByObservableNode = false;
		boolean hasOriginalParent = false;
		boolean connectedToObservableNode = false;
		boolean hasParent = false;
		// First rule: if any parent of e is an original then e is an original
		// Second rule: if e connect to an observable node, and e doesn't have any parent, then e is an original.
		// Third rule: if e is connected by an observable node, and none of its parents is original, then e is an evolved one.
		
		//if (!e.getElementType().equals(REQUIREMENT)) return;
		
		boolean currentOriginal = ORIGIN_MODEL_ELEMENT.getValue(e); //e.getProperty(LanguageElementField.BOOLEAN, ORIGIN_MODEL_ELEMENT);
		
		for(Relation rel: e.getIncoming()) {
			Element source = resolveSourceElement(rel);
			if (source != null) {
				if (source.getElementType().equals(REQUIREMENT)) {
					hasOriginalParent = ORIGIN_MODEL_ELEMENT.getValue(source);// source.getProperty(LanguageElementField.BOOLEAN, ORIGIN_MODEL_ELEMENT);
					hasParent = true;
				}
				else if (source.getElementType().equals(OBSERVABLE))  
					connectedByObservableNode = true;
				if (hasOriginalParent)
						break;
			}
		}
		if (!hasOriginalParent)
			for (Relation rel: e.getOutgoing()) {
				Element target = rel.getTarget();
				if (target != null && OBSERVABLE.equals(target.getElementType())) {
					connectedToObservableNode = true;
					break;
				}
			}
		
		boolean newOriginal = hasOriginalParent || (!hasParent && (!connectedByObservableNode || connectedToObservableNode));
		
		if (currentOriginal != newOriginal) {
			//e.setProperty(LanguageElementField.BOOLEAN, ORIGIN_MODEL_ELEMENT, newOriginal);
			ORIGIN_MODEL_ELEMENT.setValue(e, newOriginal);
			// update children
			if (e.getElementType().equals(DECOMPOSE))
				for (Relation rel: e.getOutgoing())
					updateOriginality(rel.getTarget());
			else
				for (Relation rel: e.getOutgoing())
					if (rel.getTarget() != null && rel.getTarget().getElementType().equals(DECOMPOSE)) {
						Element decompose = rel.getTarget();
						UnicornDSL.ORIGIN_MODEL_ELEMENT.setValue(decompose, newOriginal);
						for (Relation rel2: decompose.getOutgoing())
							updateOriginality(rel2.getTarget());
					}
		}
	}
	
	public Element resolveSourceElement(Relation rel) {
		if (rel.getSource() != null) {
			if (rel.getSource().getElementType().equals(DECOMPOSE)) {
				if (rel.getSource().getIncoming().size() > 0)
					return rel.getSource().getIncoming().get(0).getSource();
			}
		}
		return rel.getSource();
	}

}
